package YAPserver.logic;

import java.util.ArrayList;
import java.util.HashMap;

//import YAPserver.gameLogic.GameModel.GameState;
import YAPenvironment.YAPconstant;
import YAPserver.gameObjects.GameBall;
import YAPserver.gameObjects.Mod;
import YAPserver.gameObjects.Slider;
import YAPserver.gameObjects.GameWall;
import YAPserver.score.Award;
import YAPserver.score.CountingAward;
import YAPserver.score.EndScore;

/**
 * GamePlay handles the course of action during the game. It has a GameModel that
 * defines the rules by which will be played.
 * @author Daniel
 *
 */

public class GamePlay {
	GameModel m_model;
	private HashMap<Integer, Player> m_players = new HashMap<Integer, Player>();
	private ArrayList<GameBall> m_balls = new ArrayList<GameBall>(); //lists all gameBalls
	private ArrayList<Mod> m_modsMoving = new ArrayList<Mod>(); //lists all mods that are still moving through the field
	private ArrayList<Mod> m_modsActive = new ArrayList<Mod>(); //lists all mods that have been activated and are no longer moving
	private ArrayList<Slider> m_sliders = new ArrayList<Slider>();
	private ArrayList<GameWall> m_walls = new ArrayList<GameWall>();
	
	private GameState m_gameState;
	private Input m_in;
	private GameModifier m_gameMod;
	private Field m_field;
	private boolean endOfGame = false;
	private int alivePlayers;
	private int gameTime;
	private EndScore endScore;
	
	/**
	 * Constructs a new GamePlay.
	 * @param model game model that will be used to play.
	 * @param players a hashMap of players who will play the game
	 * @param playersSliders a hashMap of players and their sliders
	 * @param field the field that will be played in
	 * @param in the input
	 * @param out the output
	 */
	public GamePlay(GameModel model, HashMap<Integer, Player> players,
			HashMap<Integer, Integer> playersSliders,
			Field field, Input in, Output out){
		m_model = model;
		m_players = players;
		m_field = field;
		m_walls = m_field.getWalls();
		m_in = in;
		//should be done in the constructor of Input
		m_in.setPlayers(m_players);
		m_gameState = new GameState(out);
		
		m_model.assignPlayers(m_players, playersSliders, m_sliders, m_walls);
		m_model.addBall(m_balls);
		
		m_gameMod = new GameModifier(this, out);
		m_gameState.updateObjectPos(m_sliders, m_balls, m_modsMoving, m_walls);
		endScore = new EndScore(out);
		alivePlayers = m_players.size();
		
		for(CountingAward award: CountingAward.values()){
			award.reset(m_players);
		}
		
		linkPlayersObservers(out);
		linkWallOutput(out);
		//TODO: perhaps implement setOutput(out) in IGameModel???
		((AbstractModel) m_model).setOutput(out);
	}

	/**
	 * Plays through one round of YAP. This involves moving any moveable objects checking for collisions adding mods and cleaning up. When there is only one
	 * player left it returns that player's id and otherwise will return -1.
	 * @return -1 if there is (not yet) a winner and otherwise the winner of the game
	 * @see #cleanUp()
	 */
	public int playOneRound() {
		m_in.sendCommands();	//sets the MoveCommands of every player (still) in the game
		m_model.checkCollisions(m_balls, m_modsMoving, m_walls, m_sliders);		//checks collisions between all balls and objects
		m_model.moveBalls(m_balls, m_modsMoving);			//moves all the balls (including mods)
		m_model.moveSliders(m_players);			//moves all the sliders
		m_model.addMod(m_modsMoving, m_gameMod);			//adds a mod every #ticks
		int winningId = cleanUp();				//cleans up the game, deletes objects that are no longer needed
		m_in.setPlayers(m_players); //ensures the list of players in Input is synchronised with this list of players
		time();
		return winningId;
	}
	
	/**
	 * Deletes balls that are no longer in the playing field. Deletes players who have less than the minimum number of lives.
	 * Checks if there are any balls left in the field and if not adds a new one. Updates the {@link GameState}. If there is one player left
	 * then the method returns that player's id and otherwise -1
	 * @return
	 */
	public int cleanUp() {
		m_model.tickActiveMods(m_modsActive);
		m_model.deleteBall(m_balls, m_modsMoving);
		m_model.deletePlayer(m_players, m_sliders, m_in, this);
		if(m_balls.size()==0){
			m_model.addBall(m_balls);
		}
		m_gameState.updateObjectPos(m_sliders, m_balls, m_modsMoving, m_walls);
		//this could come up somewhere else
//		m_gameState.updatePlayerStat(m_players);
		if(this.alivePlayers<=1){
//		if(m_players.size()<=1){
			endOfGame = true;
			//there should only be one player left
			int id = endScore.giveAwards(m_players);
			return id;
		}
		return -1;
	}
	
	private void linkWallOutput(Output out){
		for(GameWall wall: m_walls){
			wall.setOutput(out);
		}
		for(Slider slider: m_sliders){
			slider.setOutput(out);
		}
	}
	
	private void linkPlayersObservers(Output out){
		for(int id: m_players.keySet()){
			m_players.get(id).addObserver(out);
		}
	}
	
	private void time(){
		gameTime++;
		if(30*gameTime/Game.m_fps > 9000){
			for(int id: m_players.keySet()){
				if(m_players.get(id).getLives() > YAPconstant.C_MINLIVES){
					m_players.get(id).score(CountingAward.Chronos);
				}
			}
		}
	}
	
	/**
	 * Sets the number of players still alive.
	 * @param alive number of players still alive
	 */
	public void setPlayersAlive(int alive){
		alivePlayers = alive;
	}
	
	/**
	 * @return true if the game has not yet ended and false if it has
	 */
	public boolean hasNotEnded(){
		if(endOfGame){
			return false;
		}else{
			return true;
		}
	}
	//requires some code cleaning

	public Player getPlayer(int id) {
		return m_players.get(id);
	}

	public GameBall getBall(int i) {
		return m_balls.get(i);
	}

	public Mod getModMoving(int i) {
		return m_modsMoving.get(i);
	}

	public Mod getModActive(int i) {
		return m_modsActive.get(i);
	}

	public Slider getSlider(int i) {
		return m_sliders.get(i);
	}

	public GameWall getWall(int i) {
		return m_walls.get(i);
	}
	
	public ArrayList<GameBall> getBalls(){
		return m_balls;
	}
	
	public ArrayList<Mod> getModsMoving(){
		return m_modsMoving;
	}
	
	public ArrayList<Mod> getModsActive(){
		return m_modsActive;
	}
	
	public ArrayList<Slider> getSliders(){
		return m_sliders;
	}
	
	public GameModel getGameModel(){
		return m_model;
	}

	public ArrayList<GameWall> getWalls() {
		return m_walls;
	}

	public HashMap<Integer, Player> getPlayers() {
		return m_players;
	}

	public GameModifier getGameModifier() {
		return m_gameMod;
	}
}
